// =====================================================================================
// 
//       Filename:  window.cpp
// 
//    Description:  Implémentation de l'interface graphique
// 
//        Version:  1.0
//        Created:  24/06/2009 22:11:39
//       Revision:  none
//       Compiler:  g++
// 
//         Author:  François Hissel (fh), francois.hissel@m4x.org
//        Company:  
// 
// =====================================================================================

#include	<gtkmm/main.h>
#include	<gtkmm/eventbox.h>
#include	<gtkmm/window.h>
#include	<gtkmm/scrolledwindow.h>
#include	<gtkmm/drawingarea.h>
#include	<gtkmm/box.h>
#include	<gtkmm/button.h>
#include	<gtkmm/entry.h>
#include	<gtkmm/label.h>
#include	<gtkmm/textbuffer.h>
#include	<gtkmm/textview.h>
#include	<gtkmm/dialog.h>
#include	<gtkmm/messagedialog.h>
#include	<gtkmm/frame.h>
#include	<gtkmm/checkbutton.h>
#include	<cairomm/context.h>
#include	<deque>
#include	<list>
#include	<vector>
#include	<iostream>
#include	<stdexcept>
#include	<cmath>
#include	<cfloat>
#include	"erreurs.h"
#include	"raster.h"
#include	"geometrie.h"
#include	"graphe.h"
#include	"gisobjects.h"
#include	"routes.h"
#include	"application.h"
#include	"window.h"

/*************************************/
/*  IMPLEMENTATION DE LA CLASSE ZONE */
/*************************************/
Zone::Zone(Fenetre *f):fenetre(f),dessine_raster(0) {
#ifndef GLIBMM_DEFAULT_SIGNAL_HANDLERS_ENABLED
	signal_expose_event().connect(sigc::mem_fun(*this,&Zone::on_expose_event),false);
#endif
}

Zone::~Zone() {
}

void Zone::trace_raster(Cairo::RefPtr<Cairo::Context> cr) {
	if (!fenetre->app->raster) return;
	double x=fenetre->app->raster->xmin();
	double y;
	double cellsize=fenetre->app->raster->cellule()*fenetre->app->ratio+1;
	for (int i=0;i<fenetre->app->raster->taillex();i++) {
		y=fenetre->app->raster->ymin();
		for (int j=0;j<fenetre->app->raster->tailley();j++) {
			RGB couleur=fenetre->app->raster->couleur(i,j);
			cr->set_source_rgb(couleur.r,couleur.g,couleur.b);
			cr->rectangle(fenetre->app->get_x(x),fenetre->app->get_y(y),cellsize,cellsize);
			cr->fill();
			y+=fenetre->app->raster->cellule();
		}
		x+=fenetre->app->raster->cellule();
	}
}

void Zone::invalide_raster() {
	delete fenetre->app->raster;
	fenetre->app->raster=0;
}

bool Zone::on_expose_event(GdkEventExpose *event) {
	// Calcul du ratio d'affichage pour que la carte soit visible en entier
	Glib::RefPtr<Gdk::Window> window=get_window();
	Gtk::Allocation allocation=get_allocation();
	width=allocation.get_width();
	height=allocation.get_height();
	fenetre->app->calcule_ratio(width,height,true);
	Cairo::RefPtr<Cairo::Context> cr=window->create_cairo_context();
	// Restriction du dessin à la zone à rafraîchir
	if (event) {
		cr->rectangle(event->area.x,event->area.y,event->area.width,event->area.height);
		cr->clip();
	}
	// Effaçage du tracé actuel
	cr->set_source_rgb(1.0,1.0,1.0);
	cr->paint();
	// Calcul et dessin éventuel de la carte raster
	if (dessine_raster>0) {
		if (!fenetre->app->raster) {
			if (dessine_raster==1) fenetre->app->dessine_accessibilite((fenetre->app->w_xmax-fenetre->app->w_xmin)/fenetre->app->raster_size,sqrt(width*width+height*height)/fenetre->app->ratio);
			else if (dessine_raster==2) fenetre->app->dessine_desserte((fenetre->app->w_xmax-fenetre->app->w_xmin)/fenetre->app->raster_size);
		}
		trace_raster(cr);
	}
	// Dessin du réseau routier
	cr->set_line_width(0.5);
	cr->save();
	for (vector<Arc*>::iterator itt=fenetre->app->reseau->arcs.begin();itt!=fenetre->app->reseau->arcs.end();itt++) {
		Troncon* pl=((Troncon*)(*itt));
		if (pl->num_points()>=2) {
			if (pl->tag!=0) {	// Si le tag est non nul, l'arc est sur un itinéraire à représenter. Dans ce cas, on change le style du tracé
				RGB coul=RGB::palette((pl->tag+3) % 15);
				cr->set_source_rgb(coul.r,coul.g,coul.b);
				cr->set_line_width(2);
			} else if (pl->coupe) {	// Si le poids est trop important, l'itinéraire est coupé et on le représente dans une autre couleur
				cr->set_source_rgb(0.0,0.0,1.0);
				cr->set_line_width(0.5);
			} else { //if (((Troncon*)(*itt))->get_string(reseau->champ_sens).compare(reseau->sens_double)==0) {
				cr->set_source_rgb(0.0, 0.0, 0.0);
				cr->set_line_width(0.5);
			} /*else if (((Troncon*)(*itt))->get_string(reseau->champ_sens).compare(reseau->sens_inverse)==0) {
				cr->set_source_rgb(0.0, 1.0, 1.0);
				cr->set_line_width(0.5);
			} else if (((Troncon*)(*itt))->get_string(reseau->champ_sens).compare(reseau->sens_unique)==0) {
				cr->set_source_rgb(0.0, 1.0, 0.0);
				cr->set_line_width(0.5);
			} else {
				cr->set_source_rgb(1.0, 1.0, 0.0);
				cr->set_line_width(0.5);
			}*/
			deque<Point*>::iterator itp=pl->begin();
			cr->move_to(fenetre->app->get_x((*itp)->x),fenetre->app->get_y((*itp)->y));
			itp++;
			while (itp!=pl->end()) {
				cr->line_to(fenetre->app->get_x((*itp)->x),fenetre->app->get_y((*itp)->y));
				itp++;
			}
			cr->stroke();
		}
	}
	cr->restore();
	cr->save();
	if (def_deb) {	// On représente le point de départ de l'itinéraire par une petite croix verte
		cr->set_source_rgb(0.0,0.8,0.0);
		double xx=fenetre->app->get_x(x_deb);
		double yy=fenetre->app->get_y(y_deb);
		cr->move_to(xx-2,yy-2);cr->line_to(xx+2,yy+2);
		cr->move_to(xx+2,yy-2);cr->line_to(xx-2,yy+2);
	}
	if (def_fin) {	// On représente le point d'arrivée de l'itinéraire par une petite croix verte
		cr->set_source_rgb(0.0,0.8,0.0);
		double xx=fenetre->app->get_x(x_fin);
		double yy=fenetre->app->get_y(y_fin);
		cr->move_to(xx-2,yy-2);cr->line_to(xx+2,yy+2);
		cr->move_to(xx+2,yy-2);cr->line_to(xx-2,yy+2);
	}
	cr->stroke();
	//Tracé des points sources de la liste
	cr->set_source_rgb(0.3,0.3,1);
	for (list<Ponctuel*>::iterator it=fenetre->app->sources.begin();it!=fenetre->app->sources.end();it++) {
		double xx=fenetre->app->get_x((*it)->x);
		double yy=fenetre->app->get_y((*it)->y);
		cr->arc(xx,yy,3,0,6.2832);
		cr->fill();
	}
	//Tracé des enjeux de la liste
	cr->set_source_rgb(0.3,1,0.3);
	for (list<Ponctuel*>::iterator it=fenetre->app->enjeux.begin();it!=fenetre->app->enjeux.end();it++) {
		double xx=fenetre->app->get_x((*it)->x);
		double yy=fenetre->app->get_y((*it)->y);
		cr->arc(xx,yy,3,0,6.2832);
		cr->fill();
	}
	cr->restore();
	return true;
}

/****************************************/
/*  IMPLEMENTATION DE LA CLASSE FENETRE */
/****************************************/
Fenetre::Fenetre(Application *papp):app(papp) {
	// Définition de la fenêtre
	set_title("Itineris");
	maximize();
	// Ajustement du facteur de zoom
	if (app->w_xmin>=app->w_xmax || app->w_ymin>=app->w_ymax) throw invalid_argument("Valeurs de zoom incorrectes");
	// Définition des composants et de leur intégration
	add(sep);
	zone=new Zone(this);
	sep.set_spacing(10);
	sep.pack_start(zone_events,Gtk::PACK_EXPAND_WIDGET);
	zone_events.add_events(Gdk::BUTTON_PRESS_MASK);
	zone_events.add(*zone);
	sep.set_spacing(10);
	sep.pack_start(sep2,Gtk::PACK_SHRINK);
	btnInit.set_label("Placer le départ");
	sep2.set_homogeneous(true);
	sep2.set_spacing(10);
	sep2.pack_start(btnInit,Gtk::PACK_SHRINK);
	btnFin.set_label("Placer l'arrivée");
	sep2.pack_start(btnFin,Gtk::PACK_SHRINK);
	btnRecalculer.set_label("Itinéraire");
	sep2.pack_start(btnRecalculer,Gtk::PACK_SHRINK);
	sep2.pack_start(sep3,Gtk::PACK_SHRINK);
	labelNiveau.set_label("Niveau d'eau");
	sep3.set_spacing(10);
	sep3.pack_start(labelNiveau,Gtk::PACK_SHRINK);
	sep3.pack_start(textNiveau,Gtk::PACK_EXPAND_WIDGET);
	btnCalculerPoids.set_label("Recalculer les poids");
	sep2.pack_start(btnCalculerPoids,Gtk::PACK_SHRINK);
	btnSources.set_label("Placer les sources");
	sep2.pack_start(btnSources,Gtk::PACK_SHRINK);
	btnVideSources.set_label("Enlever les sources");
	sep2.pack_start(btnVideSources,Gtk::PACK_SHRINK);
	btnEnjeux.set_label("Placer les enjeux");
	sep2.pack_start(btnEnjeux,Gtk::PACK_SHRINK);
	btnVideEnjeux.set_label("Enlever les enjeux");
	sep2.pack_start(btnVideEnjeux,Gtk::PACK_SHRINK);
	btnTournee.set_label("Calculer la tournée");
	sep2.pack_start(btnTournee,Gtk::PACK_SHRINK);
	frameRaster.set_label("Tracé raster");
	sep2.pack_start(frameRaster,Gtk::PACK_SHRINK);
	sep4.set_homogeneous(true);
	frameRaster.add(sep4);
	btnAccessibilite.set_label("Accessibilité");
	sep4.pack_start(btnAccessibilite,Gtk::PACK_SHRINK);
	btnDesserte.set_label("Desserte");
	sep4.pack_start(btnDesserte,Gtk::PACK_SHRINK);
	btnQuitter.set_label("Quitter");
	sep2.pack_start(btnQuitter,Gtk::PACK_SHRINK);
	// Association des événements aux composants
	btnQuitter.signal_clicked().connect(sigc::mem_fun(*this,&Fenetre::btnQuitter_click_event),false);
	btnInit.signal_clicked().connect(sigc::mem_fun(*this,&Fenetre::btnInit_click_event),false);
	btnFin.signal_clicked().connect(sigc::mem_fun(*this,&Fenetre::btnFin_click_event),false);
	btnRecalculer.signal_clicked().connect(sigc::mem_fun(*this,&Fenetre::btnRecalculer_click_event),false);
	btnCalculerPoids.signal_clicked().connect(sigc::mem_fun(*this,&Fenetre::btnCalculerPoids_click_event),false);
	btnSources.signal_clicked().connect(sigc::mem_fun(*this,&Fenetre::btnSources_click_event),false);
	btnVideSources.signal_clicked().connect(sigc::mem_fun(*this,&Fenetre::btnVideSources_click_event),false);
	btnEnjeux.signal_clicked().connect(sigc::mem_fun(*this,&Fenetre::btnEnjeux_click_event),false);
	btnVideEnjeux.signal_clicked().connect(sigc::mem_fun(*this,&Fenetre::btnVideEnjeux_click_event),false);
	btnTournee.signal_clicked().connect(sigc::mem_fun(*this,&Fenetre::btnTournee_click_event),false);
	btnAccessibilite.signal_toggled().connect(sigc::mem_fun(*this,&Fenetre::btnAccessibilite_click_event),false);
	btnDesserte.signal_toggled().connect(sigc::mem_fun(*this,&Fenetre::btnDesserte_click_event),false);
	zone_events.signal_button_press_event().connect(sigc::mem_fun(*this,&Fenetre::on_button_press_event),false);
	// Affichage de la fenêtre et de tous ses composants
	show_all_children();
}

Fenetre::~Fenetre() {
	delete zone;
	zone=0;
}

void Fenetre::btnQuitter_click_event() {
	delete zone;
	zone=0;
	hide();
}

void Fenetre::btnInit_click_event() {
	zone->mode=1;
}

void Fenetre::btnFin_click_event() {
	zone->mode=2;
}

void Fenetre::btnRecalculer_click_event() {
	if ((!zone->def_deb) || (!zone->def_fin)) {
		affiche("Les points de départ et d'arrivée n'ont pas été définis",ERREUR,"Opération impossible");
		return;
	}
	app->reseau->init_couleurs();	// Efface les itinéraires précédemments calculés
	app->cherche_itineraire(zone->x_deb,zone->y_deb,zone->x_fin,zone->y_fin);
	zone->queue_draw();
}

void Fenetre::btnCalculerPoids_click_event() {
	istringstream iss(textNiveau.get_text());
	iss.exceptions(istringstream::failbit);
	double niveau;
	try {
		iss >> niveau;
		app->echelles.clear();app->niveaux.clear();
		app->niveaux.push_back(niveau);
		app->echelles.push_back(Point(0,0));
		app->calcule_poids();
		app->calcule_raster=true;	//On indique que la carte de desserte doit être recalculée
		zone->invalide_raster();	//On invalide la carte raster
		zone->queue_draw();
	} catch (exception) {
		affiche("Niveau d'eau incorrect",ERREUR,"Erreur de saisie");
	}
}

void Fenetre::btnSources_click_event() {
	zone->mode=5;
}

void Fenetre::btnVideSources_click_event() {
	zone->invalide_raster();	// On invalide la carte raster
	app->vide_sources();	// On vide la liste des sources
	app->calcule_raster=true;	// On indique que la carte raster doit être recalculée
	zone->queue_draw();	// On demande un rafraichissement de l'affichage
}

void Fenetre::btnEnjeux_click_event() {
	zone->mode=6;
}

void Fenetre::btnVideEnjeux_click_event() {
	zone->invalide_raster();	// On invalide la carte raster
	app->vide_enjeux();	// On vide la liste des enjeux
	app->calcule_raster=true;	// On indique que la carte raster doit être recalculée
	zone->queue_draw();	// On demande un rafraichissement de l'affichage
}

void Fenetre::btnTournee_click_event() {
	if (app->enjeux.size()==0 || app->sources.size()==0) {
		affiche("Cette opération nécessite la définition d'au moins une source et un enjeu",ERREUR,"Opération impossible");
		return;
	}
	app->reseau->init_couleurs();	// Efface les itinéraires précédemments calculés
	app->calcule_tournee(0,20,800,1,2,0.2);
	zone->queue_draw();
}

void Fenetre::btnAccessibilite_click_event() {
	if (!btnAccessibilite.get_active()) {
		zone->dessine_raster=0;
		return;
	}
	if (zone->dessine_raster==2) zone->invalide_raster();
	btnDesserte.set_active(false);
	// Demande de tracé sur la fenêtre
	zone->dessine_raster=1;
	zone->queue_draw();
}

void Fenetre::btnDesserte_click_event() {
	if (!btnDesserte.get_active()) {
		zone->dessine_raster=0;
		return;
	}
	if (zone->dessine_raster==1) zone->invalide_raster();
	btnAccessibilite.set_active(false);
	// Demande de tracé sur la fenêtre
	zone->dessine_raster=2;
	zone->queue_draw();
}

bool Fenetre::on_button_press_event(GdkEventButton *event) {
	if (!event) return false;
	double xx,yy;
	if (zone->mode>0) {
		// Récupération des coordonnées réelles du lieu où l'utilisateur a cliqué
		xx=app->get_xr(event->x);
		yy=app->get_yr(event->y);
	}
	if (zone->mode==1) {
		// On efface la croix précédente
		if (zone->def_deb) zone->queue_draw_area((int)(app->get_x(zone->x_deb))-2,(int)(app->get_y(zone->y_deb))-2,5,5);
		zone->x_deb=xx;
		zone->y_deb=yy;
		zone->def_deb=true;
		// On affiche la nouvelle petite croix
		zone->queue_draw_area((int)(event->x)-2,(int)(event->y)-2,4,4);
	}
	else if (zone->mode==2) {
		// On efface la croix précédente
		if (zone->def_fin) zone->queue_draw_area((int)(app->get_x(zone->x_fin))-2,(int)(app->get_y(zone->y_fin))-2,5,5);
		zone->x_fin=xx;
		zone->y_fin=yy;
		zone->def_fin=true;
		// On affiche la nouvelle petite croix
		zone->queue_draw_area((int)(event->x)-2,(int)(event->y)-2,4,4);
	}
	else if (zone->mode==5) {
		// Ajout d'un point source à la liste
		ostringstream oss;
		oss << "Source " << (app->sources.size()+1);
		app->ajoute_source(xx,yy,oss.str());
		// On invalide la carte raster
		zone->invalide_raster();
		// On indique que la carte de desserte doit être recalculée
		app->calcule_raster=true;
		// On affiche le nouveau petit cercle et on met à jour la carte raster
		if (zone->dessine_raster>0) zone->queue_draw();	// Si une couche raster est affichée, tout doit être redessiné
		else zone->queue_draw_area((int)(event->x)-3,(int)(event->y)-3,6,6);	// Sinon, on peut se contenter de ne redessiner que l'emplacement où le petit cercle a été ajouté
	}
	else if (zone->mode==6) {
		// Ajout d'un enjeu à la liste
		ostringstream oss;
		oss << "Enjeu " << (app->enjeux.size()+1);
		app->ajoute_enjeu(xx,yy,oss.str());
		// On invalide la carte raster
		zone->invalide_raster();
		// On indique que la carte de desserte doit être recalculée
		app->calcule_raster=true;
		// On affiche le nouveau petit cercle 
		zone->queue_draw_area((int)(event->x)-3,(int)(event->y)-3,6,6);
	}
	return true;
}

void Fenetre::affiche(const string &message,MessageType type,const string &titre) {
	if (type==TEXTE) {
		cout << message << endl;
		return;
	}
	Gtk::Dialog md1(titre,false);
	md1.set_default_size(400,500);
	md1.add_button("Ok",1);
	Gtk::TextView tv;
	tv.get_buffer()->set_text(message);
	tv.set_editable(false);
	Gtk::ScrolledWindow sw;
	sw.set_policy(Gtk::POLICY_AUTOMATIC,Gtk::POLICY_AUTOMATIC);
	sw.add(tv);
	md1.get_vbox()->pack_start(sw,Gtk::PACK_EXPAND_WIDGET);
	md1.show_all_children();
	md1.run();
}

void Fenetre::affiche_erreur(const string &message,MessageType type,const string &titre) {
	cerr << message << "\n";
}

/****************************************/
/*  IMPLEMENTATION DES AUTRES FONCTIONS */
/****************************************/
void openWindow(Application *papp) {
	void (*sav_affiche)(const string &message,MessageType type,const string &titre)=papp->affiche;
	void (*sav_affiche_erreur)(const string &message,MessageType type,const string &titre)=papp->affiche_erreur;
	papp->affiche=&Fenetre::affiche;
	papp->affiche_erreur=&Fenetre::affiche_erreur;
	Gtk::Main kit(papp->argc, papp->argv);
	Fenetre win(papp);
	Gtk::Main::run(win);
	papp->affiche=sav_affiche;
	papp->affiche_erreur=sav_affiche_erreur;
}

